/*-------------------------------------------------------------------------
 *
 * be-secure-gssapi.c
 *  GSSAPI加密支持
 *
 * Portions Copyright (c) 2018-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *  src/backend/libpq/be-secure-gssapi.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <unistd.h>

#include "libpq/auth.h"
#include "libpq/be-gssapi-common.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "utils/memutils.h"


/*
 * 使用 GSSAPI 处理数据的加密/解密。
 *
 * 在网络上的加密数据流中，我们将数据
 * 分成数据包，每个数据包以一个 uint32 大小的长度
 * 字（网络字节序）开始，接着是该长度的加密数据
 * 紧随其后。解密将得到未使用加密时
 * 显示的相同数据流。
 *
 * 加密数据通常比未加密的同样数据
 * 大，因此我们使用固定大小的缓冲区来处理
 * 加密/解密，这些缓冲区通常会比 PQComm 的缓冲区要大，以最小化我们需要
 * 创建多个数据包（因此对单个
 * 读/写调用进行多次 recv/send 调用）的次数。
 *
 * 注意：客户端和服务器必须就最大数据包大小达成一致，
 * 因为我们必须将整个数据包一次性传递给 GSSAPI，
 * 我们不希望另一方发送任意巨大的数据包，因为我们将必须为它们分配内存然后再传递给 GSSAPI。
 *
 * 因此，这两个 #define 实际上是协议
 * 规格的一部分，不能被修改。
 */
#define PQ_GSS_SEND_BUFFER_SIZE 16384
#define PQ_GSS_RECV_BUFFER_SIZE 16384

/*
 * 由于我们每个后端最多管理一个 GSS 加密连接，
 * 因此可以将所有状态保存在静态变量中。char *
 * 变量指向一次分配并重复使用的缓冲区。
 */
static char *PqGSSSendBuffer;	/* 等待发送的加密数据 */
static int	PqGSSSendLength;	/* PqGSSSendBuffer 中可用数据的结束 */
static int	PqGSSSendNext;		/* 发送字节的下一个索引
								 * PqGSSSendBuffer */
static int	PqGSSSendConsumed;	/* 源字节数已加密但尚未
								 * 报告为已发送 */

static char *PqGSSRecvBuffer;	/* 接收到的加密数据 */
static int	PqGSSRecvLength;	/* PqGSSRecvBuffer 中可用数据的结束 */

static char *PqGSSResultBuffer; /* 在 gss_RecvBuffer 中的数据解密 */
static int	PqGSSResultLength;	/* PqGSSResultBuffer 中可用数据的结束 */
static int	PqGSSResultNext;	/* 从 PqGSSResultBuffer 中读取字节的下一个索引 */

static uint32 PqGSSMaxPktSize;	/* 我们可以加密并将结果适配到输出缓冲区的最大大小 */


/*
 * 尝试从指针 ptr 向 GSSAPI 加密连接写入 len 字节的数据。
 *
 * 连接必须已经配置为 GSSAPI 加密（即，GSSAPI
 * 传输协商已经完成）。
 *
 * 成功时，返回消耗的数据字节数（可能少于 len）。
 * 失败时，返回 -1，并适当地设置 errno。
 * 对于可重试的错误，调用者应在套接字准备就绪时
 * 再次调用（传递相同或更多数据）。
 *
 * 处理致命错误在这里有点棘手：我们不能调用 elog(FATAL)
 * 因为这会尝试写入客户端，可能导致无限递归。
 * 相反，使用 elog(COMMERROR) 记录关于
 * 失败的额外信息（如有必要），然后返回表示连接丢失的 errno。
 */
ssize_t be_gssapi_write(Port *fc_port, void *fc_ptr, size_t fc_len)
{
	OM_uint32	fc_major,
				fc_minor;
	gss_buffer_desc fc_input,
				fc_output;
	size_t		fc_bytes_to_encrypt;
	size_t		fc_bytes_encrypted;
	gss_ctx_id_t fc_gctx = fc_port->gss->ctx;

	/*
	 * 当我们遇到可重试的失败时，必须不告诉调用者我们已
	 * 成功传输了所有内容，否则它将不会重试。为了
	 * 简便起见，我们声称在成功传输所有 "len" 字节之前，
	 * 我们没有传输任何内容。在调用之间，当前输入数据中已经
	 * 被加密并放入 PqGSSSendBuffer（并可能已传输）的数量
	 * 存储在 PqGSSSendConsumed 中。在重试时，调用者
	 * *必须* 重新发送该数据，因此如果它提供的 len 小于该值，说明出现了问题。
	 *
	 * 注意：在我们成功发送任何加密数据包后报告部分写入完成似乎很有吸引力。
	 * 然而，这会给调用者带来问题；特别是，pqPutMsgEnd 的启发式发送只完整的 8K 块与这样的黑客方法相互作用很糟糕。
	 * 允许调用者提前丢弃数据通常不会节省太多，因此不要冒这个险。
	 */
	if (fc_len < PqGSSSendConsumed)
	{
		elog(COMMERROR, "GSSAPI caller failed to retransmit all data needing to be retried");
		errno = ECONNRESET;
		return -1;
	}

	/* 抵消我们已经加密的任何源数据。 */
	fc_bytes_to_encrypt = fc_len - PqGSSSendConsumed;
	fc_bytes_encrypted = PqGSSSendConsumed;

	/*
	 * 循环加密数据并发送出去，直到完成或
	 * secure_raw_write() 报告问题（这可能意味着套接字
	 * 是非阻塞的，所请求的send()将阻塞，或者有某种
	 * 实际错误）。
	 */
	while (fc_bytes_to_encrypt || PqGSSSendLength)
	{
		int			fc_conf_state = 0;
		uint32		fc_netlen;

		/*
		 * 检查加密输出缓冲区中是否有需要发送的数据（可能是
		 * 从之前的调用中留下的），如果有，尝试发送它。如果我们
		 * 不能发送，将这一事实返回给调用者。
		 */
		if (PqGSSSendLength)
		{
			ssize_t		fc_ret;
			ssize_t		fc_amount = PqGSSSendLength - PqGSSSendNext;

			fc_ret = secure_raw_write(fc_port, PqGSSSendBuffer + PqGSSSendNext, fc_amount);
			if (fc_ret <= 0)
				return fc_ret;

			/*
			 * 检查这是否是部分写入，如果是，向前移动到
			 * 我们的缓冲区，并重新尝试。
			 */
			if (fc_ret < fc_amount)
			{
				PqGSSSendNext += fc_ret;
				continue;
			}

			/* 我们已经成功发送了缓冲区中的所有数据。 */
			PqGSSSendLength = PqGSSSendNext = 0;
		}

		/*
		 * 检查是否还有字节待加密。如果没有，我们就完成了。
		 */
		if (!fc_bytes_to_encrypt)
			break;

		/*
		 * 检查我们被要求发送多少数据，如果太多，
		 * 那么我们必须循环并可能被多次调用以发送
		 * 所有数据。
		 */
		if (fc_bytes_to_encrypt > PqGSSMaxPktSize)
			fc_input.length = PqGSSMaxPktSize;
		else
			fc_input.length = fc_bytes_to_encrypt;

		fc_input.value = (char *) fc_ptr + fc_bytes_encrypted;

		fc_output.value = NULL;
		fc_output.length = 0;

		/*
		 * 创建下一个加密数据包。这里的任何失败都视为
		 * 严重失败，因此即使已发送一些数据，我们也返回 -1。
		 */
		fc_major = gss_wrap(&fc_minor, fc_gctx, 1, GSS_C_QOP_DEFAULT,
						 &fc_input, &fc_conf_state, &fc_output);
		if (fc_major != GSS_S_COMPLETE)
		{
			pg_GSS_error(_("GSSAPI wrap error"), fc_major, fc_minor);
			errno = ECONNRESET;
			return -1;
		}
		if (fc_conf_state == 0)
		{
			ereport(COMMERROR,
					(errmsg("outgoing GSSAPI message would not use confidentiality")));
			errno = ECONNRESET;
			return -1;
		}
		if (fc_output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
		{
			ereport(COMMERROR,
					(errmsg("server tried to send oversize GSSAPI packet (%zu > %zu)",
							(size_t) fc_output.length,
							PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))));
			errno = ECONNRESET;
			return -1;
		}

		fc_bytes_encrypted += fc_input.length;
		fc_bytes_to_encrypt -= fc_input.length;
		PqGSSSendConsumed += fc_input.length;

		/* 4 个网络序字节的长度，然后是有效负载 */
		fc_netlen = pg_hton32(fc_output.length);
		memcpy(PqGSSSendBuffer + PqGSSSendLength, &fc_netlen, sizeof(uint32));
		PqGSSSendLength += sizeof(uint32);

		memcpy(PqGSSSendBuffer + PqGSSSendLength, fc_output.value, fc_output.length);
		PqGSSSendLength += fc_output.length;

		/* 释放 GSSAPI 分配的缓冲区存储 */
		gss_release_buffer(&fc_minor, &fc_output);
	}

	/* 如果我们到这里，所有的计数器应该匹配。 */
	Assert(fc_len == PqGSSSendConsumed);
	Assert(fc_len == fc_bytes_encrypted);

	/* 我们报告所有数据已发送，因此重置 PqGSSSendConsumed。 */
	PqGSSSendConsumed = 0;

	return fc_bytes_encrypted;
}

/*
 * 从已建立GSSAPI加密连接中读取最多len字节的数据到ptr。
 *
 * 该连接必须已经为GSSAPI加密设置好（即，GSSAPI
 * 传输协商已完成）。
 *
 * 返回读取的数据字节数，或者在失败时返回-1，
 * 同时errno适当设置。对于可重试的错误，调用者应该在
 * 套接字准备好后再次调用。
 *
 * 我们将致命错误与be_gssapi_write()中的处理方式相同，
 * 即使关于无限递归的论点在这里不适用。
 */
ssize_t be_gssapi_read(Port *fc_port, void *fc_ptr, size_t fc_len)
{
	OM_uint32	fc_major,
				fc_minor;
	gss_buffer_desc fc_input,
				fc_output;
	ssize_t		fc_ret;
	size_t		fc_bytes_returned = 0;
	gss_ctx_id_t fc_gctx = fc_port->gss->ctx;

	/*
	 * 这里的计划是读取一个传入的加密数据包到
	 * PqGSSRecvBuffer，将其解密到 PqGSSResultBuffer，然后将
	 * 数据分发给调用者。当当前输入数据包用完时，读取另一个。
	 */
	while (fc_bytes_returned < fc_len)
	{
		int			fc_conf_state = 0;

		/* 检查我们是否有可以立即返回的数据在我们的缓冲区中 */
		if (PqGSSResultNext < PqGSSResultLength)
		{
			size_t		fc_bytes_in_buffer = PqGSSResultLength - PqGSSResultNext;
			size_t		fc_bytes_to_copy = Min(fc_bytes_in_buffer, fc_len - fc_bytes_returned);

			/*
			 * 将数据从我们的结果缓冲区复制到调用者的缓冲区，
			 * 在我们最后填充他们的缓冲区的地方。
			 */
			memcpy((char *) fc_ptr + fc_bytes_returned, PqGSSResultBuffer + PqGSSResultNext, fc_bytes_to_copy);
			PqGSSResultNext += fc_bytes_to_copy;
			fc_bytes_returned += fc_bytes_to_copy;

			/*
			 * 此时，我们要么填满了调用者的缓冲区，要么
			 * 清空了我们的结果缓冲区。无论哪种情况，返回给调用者。
			 * 在第二种情况下，我们可以尝试读取另一个加密数据包，
			 * 但很有可能没有可用的。 （如果这种情况不成立，
			 * 我们选择了过小的最大数据包大小。）无论如何，
			 * 让调用者处理我们已经返回的数据都是没有害处的。
			 */
			break;
		}

		/* 结果缓冲区为空，因此重置缓冲指针 */
		PqGSSResultLength = PqGSSResultNext = 0;

		/*
		 * 因为我们选择在发出一些数据后立即返回
		 * ，此时 bytes_returned 必须为零。因此
		 * 下面的故障退出可以直接返回 -1，而不必担心
		 * 我们是否已经发出了一些数据。
		 */
		Assert(fc_bytes_returned == 0);

		/*
		 * 此时，我们的结果缓冲区为空，并且请求读取更多字节。
		 * 我们现在准备加载下一个数据包并
		 * 完全解密到我们的结果缓冲区中。
		 */

		/* 如果我们还没有收集长度，则收集长度 */
		if (PqGSSRecvLength < sizeof(uint32))
		{
			fc_ret = secure_raw_read(fc_port, PqGSSRecvBuffer + PqGSSRecvLength,
								  sizeof(uint32) - PqGSSRecvLength);

			/* 如果 ret <= 0，secure_raw_read 已经设置了正确的 errno */
			if (fc_ret <= 0)
				return fc_ret;

			PqGSSRecvLength += fc_ret;

			/* 如果我们仍然没有得到长度，返回给调用者 */
			if (PqGSSRecvLength < sizeof(uint32))
			{
				errno = EWOULDBLOCK;
				return -1;
			}
		}

		/* 解码数据包长度并检查是否为超长数据包 */
		fc_input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer);

		if (fc_input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
		{
			ereport(COMMERROR,
					(errmsg("oversize GSSAPI packet sent by the client (%zu > %zu)",
							(size_t) fc_input.length,
							PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))));
			errno = ECONNRESET;
			return -1;
		}

		/*
		 * 在此调用中尽可能多地读取数据包，
		 * 从我们上一次被调用时离开的地方开始读取。
		 */
		fc_ret = secure_raw_read(fc_port, PqGSSRecvBuffer + PqGSSRecvLength,
							  fc_input.length - (PqGSSRecvLength - sizeof(uint32)));
		/* 如果 ret <= 0，secure_raw_read 已经设置了正确的 errno */
		if (fc_ret <= 0)
			return fc_ret;

		PqGSSRecvLength += fc_ret;

		/* 如果我们还没有完整的数据包，返回给调用者 */
		if (PqGSSRecvLength - sizeof(uint32) < fc_input.length)
		{
			errno = EWOULDBLOCK;
			return -1;
		}

		/*
		 * 我们现在拥有完整的数据包，可以进行解密并
		 * 重新填充我们的结果缓冲区，然后循环返回
		 * 数据给调用者。
		 */
		fc_output.value = NULL;
		fc_output.length = 0;
		fc_input.value = PqGSSRecvBuffer + sizeof(uint32);

		fc_major = gss_unwrap(&fc_minor, fc_gctx, &fc_input, &fc_output, &fc_conf_state, NULL);
		if (fc_major != GSS_S_COMPLETE)
		{
			pg_GSS_error(_("GSSAPI unwrap error"), fc_major, fc_minor);
			errno = ECONNRESET;
			return -1;
		}
		if (fc_conf_state == 0)
		{
			ereport(COMMERROR,
					(errmsg("incoming GSSAPI message did not use confidentiality")));
			errno = ECONNRESET;
			return -1;
		}

		memcpy(PqGSSResultBuffer, fc_output.value, fc_output.length);
		PqGSSResultLength = fc_output.length;

		/* 我们的接收缓冲区现在为空，重置它 */
		PqGSSRecvLength = 0;

		/* 释放 GSSAPI 分配的缓冲区存储 */
		gss_release_buffer(&fc_minor, &fc_output);
	}

	return fc_bytes_returned;
}

/*
 * 从网络读取指定数量的字节，如果会阻塞则使用
 * WaitLatchOrSocket 等待。
 *
 * 结果读取到 PqGSSRecvBuffer 中。
 *
 * 将始终返回 -1，表示永久错误，或 len。
 */
static ssize_t fc_read_or_wait(Port *fc_port, ssize_t fc_len)
{
	ssize_t		fc_ret;

	/*
	 * 继续进行，直到我们要么读取了所有请求的字节，要么出现错误。
	 */
	while (PqGSSRecvLength < fc_len)
	{
		fc_ret = secure_raw_read(fc_port, PqGSSRecvBuffer + PqGSSRecvLength, fc_len - PqGSSRecvLength);

		/*
		 * 如果我们返回了一个错误，并且它不是简单的
		 * EWOULDBLOCK/EAGAIN/EINTR，则放弃。
		 */
		if (fc_ret < 0 &&
			!(errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR))
			return -1;

		/*
		 * 好的，我们返回了一个正值、零或负结果
		 * 表示我们应该重试。
		 *
		 * 如果是零或负数，则我们等待该套接字变为可读。
		 */
		if (fc_ret <= 0)
		{
			WaitLatchOrSocket(MyLatch,
							  WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH,
							  fc_port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);

			/*
			 * 如果我们收到零字节，然后在套接字上等待可读并在第二次读取中得到零字节，那么这就是
			 * EOF，并且客户端已经挂断了我们。
			 *
			 * 如果我们在这里得到了数据，那么我们可以继续处理
			 * 就像第一次得到数据一样。
			 *
			 * 否则返回顶部重新尝试。
			 */
			if (fc_ret == 0)
			{
				fc_ret = secure_raw_read(fc_port, PqGSSRecvBuffer + PqGSSRecvLength, fc_len - PqGSSRecvLength);
				if (fc_ret == 0)
					return -1;
			}
			if (fc_ret < 0)
				continue;
		}

		PqGSSRecvLength += fc_ret;
	}

	return fc_len;
}

/*
 * 启动一个GSSAPI加密的连接。 这执行GSSAPI
 * 身份验证；在此函数完成后，可以安全地调用
 * be_gssapi_read和be_gssapi_write。 失败时返回-1并记录日志；
 * 否则返回0并将连接标记为准备好进行GSSAPI
 * 加密。
 *
 * 请注意，与be_gssapi_read/be_gssapi_write函数不同，
 * 此函数将在套接字上阻塞，以便可以根据需要准备进行读/写
 * (使用WaitLatchOrSocket)建立GSSAPI
 * 会话。
 */
ssize_t secure_open_gssapi(Port *fc_port)
{
	bool		fc_complete_next = false;
	OM_uint32	fc_major,
				fc_minor;

	/*
	 * 为GSSAPI操作分配辅助端口数据。
	 */
	fc_port->gss = (pg_gssinfo *)
		MemoryContextAllocZero(TopMemoryContext, sizeof(pg_gssinfo));

	/*
	 * 分配缓冲区并初始化状态变量。通过在这一点上malloc缓冲区，
	 * 我们可以避免在永远不会使用它们的进程中浪费静态数据空间，
	 * 并确保缓冲区在文件中的某些地方用于长度字的访问时
	 * 足够对齐。
	 */
	PqGSSSendBuffer = malloc(PQ_GSS_SEND_BUFFER_SIZE);
	PqGSSRecvBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE);
	PqGSSResultBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE);
	if (!PqGSSSendBuffer || !PqGSSRecvBuffer || !PqGSSResultBuffer)
		ereport(FATAL,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));
	PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0;
	PqGSSRecvLength = PqGSSResultLength = PqGSSResultNext = 0;

	/*
	 * 如果有已配置的keytab，则使用它。不幸的是，Heimdal
	 * 不支持凭据存储扩展，因此使用环境变量。
	 */
	if (pg_krb_server_keyfile != NULL && pg_krb_server_keyfile[0] != '\0')
	{
		if (setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1) != 0)
		{
			/* 唯一可能的失败原因是OOM，因此使用该错误代码 */
			ereport(FATAL,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("could not set environment: %m")));
		}
	}

	while (true)
	{
		ssize_t		fc_ret;
		gss_buffer_desc fc_input,
					fc_output = GSS_C_EMPTY_BUFFER;

		/*
		 * 客户端总是先发送，因此尝试提前读取
		 * 长度，并在失败时等待套接字再次可读。
		 */
		fc_ret = fc_read_or_wait(fc_port, sizeof(uint32));
		if (fc_ret < 0)
			return fc_ret;

		/*
		 * 从长度头获取此数据包的长度。
		 */
		fc_input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer);

		/* 完成长度，重置我们的缓冲区 */
		PqGSSRecvLength = 0;

		/*
		 * 在初始化期间，数据包总是被完全消耗，并且
		 * 不应该超过PQ_GSS_RECV_BUFFER_SIZE的长度。
		 *
		 * 确认我们这一方客户端没有做什么奇怪的事情。
		 */
		if (fc_input.length > PQ_GSS_RECV_BUFFER_SIZE)
		{
			ereport(COMMERROR,
					(errmsg("oversize GSSAPI packet sent by the client (%zu > %d)",
							(size_t) fc_input.length,
							PQ_GSS_RECV_BUFFER_SIZE)));
			return -1;
		}

		/*
		 * 获取剩余的数据包，以便将其传递给GSSAPI以接受
		 * 上下文。
		 */
		fc_ret = fc_read_or_wait(fc_port, fc_input.length);
		if (fc_ret < 0)
			return fc_ret;

		fc_input.value = PqGSSRecvBuffer;

		/* 处理传入数据。 （客户端先发送。） */
		fc_major = gss_accept_sec_context(&fc_minor, &fc_port->gss->ctx,
									   GSS_C_NO_CREDENTIAL, &fc_input,
									   GSS_C_NO_CHANNEL_BINDINGS,
									   &fc_port->gss->name, NULL, &fc_output, NULL,
									   NULL, NULL);
		if (GSS_ERROR(fc_major))
		{
			pg_GSS_error(_("could not accept GSSAPI security context"),
						 fc_major, fc_minor);
			gss_release_buffer(&fc_minor, &fc_output);
			return -1;
		}
		else if (!(fc_major & GSS_S_CONTINUE_NEEDED))
		{
			/*
			 * rfc2744在技术上允许上下文谈判通过
			 * 发送和不发送数据包两种方式完成。
			 */
			fc_complete_next = true;
		}

		/* 完成处理传入数据包，重置我们的缓冲区 */
		PqGSSRecvLength = 0;

		/*
		 * 检查我们是否有数据要发送，如果有，确保发送
		 * 所有数据
		 */
		if (fc_output.length > 0)
		{
			uint32		fc_netlen = pg_hton32(fc_output.length);

			if (fc_output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
			{
				ereport(COMMERROR,
						(errmsg("server tried to send oversize GSSAPI packet (%zu > %zu)",
								(size_t) fc_output.length,
								PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))));
				gss_release_buffer(&fc_minor, &fc_output);
				return -1;
			}

			memcpy(PqGSSSendBuffer, (char *) &fc_netlen, sizeof(uint32));
			PqGSSSendLength += sizeof(uint32);

			memcpy(PqGSSSendBuffer + PqGSSSendLength, fc_output.value, fc_output.length);
			PqGSSSendLength += fc_output.length;

			/* 我们在这里不处理PqGSSSendConsumed */

			while (PqGSSSendNext < PqGSSSendLength)
			{
				fc_ret = secure_raw_write(fc_port, PqGSSSendBuffer + PqGSSSendNext,
									   PqGSSSendLength - PqGSSSendNext);

				/*
				 * 如果我们返回了一个错误，而不仅仅是
				 * EWOULDBLOCK/EAGAIN/EINTR，那么就放弃。
				 */
				if (fc_ret < 0 &&
					!(errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR))
				{
					gss_release_buffer(&fc_minor, &fc_output);
					return -1;
				}

				/* 如果我们还不能写入，则等待并重试 */
				if (fc_ret <= 0)
				{
					WaitLatchOrSocket(MyLatch,
									  WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH,
									  fc_port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);
					continue;
				}

				PqGSSSendNext += fc_ret;
			}

			/* 完成发送数据包，重置我们的缓冲区 */
			PqGSSSendLength = PqGSSSendNext = 0;

			gss_release_buffer(&fc_minor, &fc_output);
		}

		/*
		 * 如果我们收到连接设置完成的消息，既然
		 * 我们已经发送了最后一个数据包，就退出我们循环。
		 */
		if (fc_complete_next)
			break;
	}

	/*
	 * 确定在考虑到长度之后，能够适应我们缓冲区的最大数据包大小。be_gssapi_write 将需要这个。
	 */
	fc_major = gss_wrap_size_limit(&fc_minor, fc_port->gss->ctx, 1, GSS_C_QOP_DEFAULT,
								PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32),
								&PqGSSMaxPktSize);

	if (GSS_ERROR(fc_major))
	{
		pg_GSS_error(_("GSSAPI size check error"), fc_major, fc_minor);
		return -1;
	}

	fc_port->gss->enc = true;

	return 0;
}

/*
 * 返回此连接是否使用了 GSSAPI 认证。
 */
bool be_gssapi_get_auth(Port *fc_port)
{
	if (!fc_port || !fc_port->gss)
		return false;

	return fc_port->gss->auth;
}

/*
 * 返回此连接是否启用了 GSSAPI 加密并正在使用中。
 */
bool be_gssapi_get_enc(Port *fc_port)
{
	if (!fc_port || !fc_port->gss)
		return false;

	return fc_port->gss->enc;
}

/*
 * 返回用于此连接身份验证的 GSSAPI 主体（如果我们没有执行 GSSAPI 认证，则为 NULL）。
 */
const char * be_gssapi_get_princ(Port *fc_port)
{
	if (!fc_port || !fc_port->gss)
		return NULL;

	return fc_port->gss->princ;
}
